package com.kris;

import java.awt.HeadlessException;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ListModel;
import javax.swing.table.DefaultTableModel;

/**
 *
 * @author zhangqunshi
 */
public class MainUI extends javax.swing.JFrame {

    private PathMappingTableModel mappingTableModel = new PathMappingTableModel();
    private ChangeFilesModel changeFilesModel = new ChangeFilesModel();
    private Map<String, String> fileMapping = new HashMap<>();

    // 由于支持单个路径映射的删除，所以需要记录路径与变动文件列表的关系
    private Map<String, List<String>> cache = new HashMap<>();
    private static final String VERSION = "v0.1";

    // 初始化查找最近更改的文件
    private long lastSyncTime = 0;

    /**
     * Creates new form ScpUI
     */
    public MainUI() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        newMappingDialog = new javax.swing.JDialog();
        localPathLabel = new javax.swing.JLabel();
        localPathTextField = new javax.swing.JTextField();
        localBrowserButton = new javax.swing.JButton();
        remotePathLabel = new javax.swing.JLabel();
        remotePathTextField = new javax.swing.JTextField();
        dialogOkButton = new javax.swing.JButton();
        mainTabbedPane = new javax.swing.JTabbedPane();
        scpPanel = new javax.swing.JPanel();
        uploadButton = new javax.swing.JButton();
        dirMappingLabel = new javax.swing.JLabel();
        deleteMappingButton = new javax.swing.JButton();
        resetButton = new javax.swing.JButton();
        mappingTableScrollPane = new javax.swing.JScrollPane();
        mappingTable = new javax.swing.JTable();
        addMappingButton = new javax.swing.JButton();
        localChangeFilesLabel = new javax.swing.JLabel();
        changeFilesScrollPane = new javax.swing.JScrollPane();
        changeFilesList = new javax.swing.JList<>();
        findChangeButton = new javax.swing.JButton();
        syncTimeLabel = new javax.swing.JLabel();
        updateSyncTimeButton = new javax.swing.JButton();
        lastSyncTimeLabel = new javax.swing.JLabel();
        currTimeButton = new javax.swing.JButton();
        configPanel = new javax.swing.JPanel();
        serverListScrollPane = new javax.swing.JScrollPane();
        hostsTextArea = new javax.swing.JTextArea();
        passwordLabel = new javax.swing.JLabel();
        showCheckBox = new javax.swing.JCheckBox();
        usernameLabel = new javax.swing.JLabel();
        usernameTextField = new javax.swing.JTextField();
        pwdTextField = new javax.swing.JPasswordField();
        serverLabel = new javax.swing.JLabel();
        jLabel1 = new javax.swing.JLabel();
        aboutMeButton = new javax.swing.JButton();

        newMappingDialog.setTitle("FilePath Mapping");
        newMappingDialog.setAlwaysOnTop(true);

        localPathLabel.setText("本地路径：");

        localBrowserButton.setText("浏览");
        localBrowserButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                localBrowserButtonActionPerformed(evt);
            }
        });

        remotePathLabel.setText("远程路径：");

        dialogOkButton.setText("确定");
        dialogOkButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                dialogOkButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout newMappingDialogLayout = new javax.swing.GroupLayout(newMappingDialog.getContentPane());
        newMappingDialog.getContentPane().setLayout(newMappingDialogLayout);
        newMappingDialogLayout.setHorizontalGroup(
            newMappingDialogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, newMappingDialogLayout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(newMappingDialogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(newMappingDialogLayout.createSequentialGroup()
                        .addComponent(localPathLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(localPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(newMappingDialogLayout.createSequentialGroup()
                        .addComponent(remotePathLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(remotePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, newMappingDialogLayout.createSequentialGroup()
                        .addGap(297, 297, 297)
                        .addComponent(dialogOkButton, javax.swing.GroupLayout.PREFERRED_SIZE, 72, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(localBrowserButton, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );
        newMappingDialogLayout.setVerticalGroup(
            newMappingDialogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, newMappingDialogLayout.createSequentialGroup()
                .addContainerGap(28, Short.MAX_VALUE)
                .addGroup(newMappingDialogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(localPathLabel)
                    .addComponent(localPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(localBrowserButton))
                .addGap(18, 18, 18)
                .addGroup(newMappingDialogLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(remotePathLabel)
                    .addComponent(remotePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(38, 38, 38)
                .addComponent(dialogOkButton)
                .addGap(21, 21, 21))
        );

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("SimpleFileSync v0.1");

        uploadButton.setMnemonic('r');
        uploadButton.setText("开始上传 (Alt+R)");
        uploadButton.setActionCommand("Upload");
        uploadButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                uploadButtonActionPerformed(evt);
            }
        });

        dirMappingLabel.setText("目录映射关系：");

        deleteMappingButton.setText("删除");
        deleteMappingButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                deleteMappingButtonActionPerformed(evt);
            }
        });

        resetButton.setText("清空");
        resetButton.setActionCommand("Reset");
        resetButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                resetButtonActionPerformed(evt);
            }
        });

        mappingTable.setModel(mappingTableModel);
        mappingTable.setSelectionMode(javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        mappingTableScrollPane.setViewportView(mappingTable);

        addMappingButton.setMnemonic('m');
        addMappingButton.setText("添加新路径映射 (Alt+M)");
        addMappingButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addMappingButtonActionPerformed(evt);
            }
        });

        localChangeFilesLabel.setText("本地变化文件列表：");

        changeFilesList.setModel(changeFilesModel);
        changeFilesScrollPane.setViewportView(changeFilesList);

        findChangeButton.setMnemonic('f');
        findChangeButton.setText("查找文件变化 (Alt+F)");
        findChangeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                findChangeButtonActionPerformed(evt);
            }
        });

        syncTimeLabel.setText("上次同步时间：");

        updateSyncTimeButton.setText("更改上次同步时间");
        updateSyncTimeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                updateSyncTimeButtonActionPerformed(evt);
            }
        });

        currTimeButton.setText("当前时间");
        currTimeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                currTimeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout scpPanelLayout = new javax.swing.GroupLayout(scpPanel);
        scpPanel.setLayout(scpPanelLayout);
        scpPanelLayout.setHorizontalGroup(
            scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, scpPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(changeFilesScrollPane, javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(mappingTableScrollPane, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 775, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, scpPanelLayout.createSequentialGroup()
                        .addComponent(dirMappingLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, scpPanelLayout.createSequentialGroup()
                                .addComponent(deleteMappingButton, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(resetButton, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE))
                            .addComponent(addMappingButton, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)))
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, scpPanelLayout.createSequentialGroup()
                        .addComponent(localChangeFilesLabel)
                        .addGap(0, 0, Short.MAX_VALUE))
                    .addGroup(scpPanelLayout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(findChangeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(scpPanelLayout.createSequentialGroup()
                        .addComponent(syncTimeLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lastSyncTimeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 172, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(updateSyncTimeButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(currTimeButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(uploadButton, javax.swing.GroupLayout.PREFERRED_SIZE, 141, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addGap(24, 24, 24))
        );

        scpPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteMappingButton, resetButton});

        scpPanelLayout.setVerticalGroup(
            scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(scpPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(addMappingButton)
                    .addComponent(dirMappingLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(mappingTableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 161, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(deleteMappingButton)
                    .addComponent(resetButton))
                .addGap(12, 12, 12)
                .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(localChangeFilesLabel)
                    .addComponent(findChangeButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(changeFilesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 251, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(18, 18, 18)
                .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(lastSyncTimeLabel, javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(scpPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(uploadButton)
                        .addComponent(syncTimeLabel)
                        .addComponent(updateSyncTimeButton)
                        .addComponent(currTimeButton)))
                .addContainerGap())
        );

        scpPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteMappingButton, resetButton});

        mainTabbedPane.addTab("同步管理", scpPanel);

        hostsTextArea.setColumns(20);
        hostsTextArea.setRows(5);
        hostsTextArea.setToolTipText("每个IP地址独占一行");
        serverListScrollPane.setViewportView(hostsTextArea);

        passwordLabel.setText("密     码:");

        showCheckBox.setText("显示");
        showCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showCheckBoxActionPerformed(evt);
            }
        });

        usernameLabel.setText("用户名:");

        serverLabel.setText("服务器列表：");

        jLabel1.setText("以上填写主机IP，每个主机IP独占一行");

        aboutMeButton.setText("关于此软件");
        aboutMeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                aboutMeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout configPanelLayout = new javax.swing.GroupLayout(configPanel);
        configPanel.setLayout(configPanelLayout);
        configPanelLayout.setHorizontalGroup(
            configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(configPanelLayout.createSequentialGroup()
                .addGap(31, 31, 31)
                .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jLabel1)
                    .addComponent(serverLabel)
                    .addGroup(configPanelLayout.createSequentialGroup()
                        .addComponent(serverListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(55, 55, 55)
                        .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(usernameLabel)
                            .addComponent(passwordLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(usernameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addGroup(configPanelLayout.createSequentialGroup()
                                .addComponent(pwdTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(showCheckBox)))))
                .addContainerGap(158, Short.MAX_VALUE))
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, configPanelLayout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(aboutMeButton)
                .addGap(42, 42, 42))
        );
        configPanelLayout.setVerticalGroup(
            configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(configPanelLayout.createSequentialGroup()
                .addGap(28, 28, 28)
                .addComponent(serverLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(configPanelLayout.createSequentialGroup()
                        .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(usernameLabel)
                            .addComponent(usernameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addGap(18, 18, 18)
                        .addGroup(configPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(passwordLabel)
                            .addComponent(pwdTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(showCheckBox)))
                    .addComponent(serverListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 323, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 122, Short.MAX_VALUE)
                .addComponent(aboutMeButton)
                .addGap(35, 35, 35))
        );

        mainTabbedPane.addTab("服务器配置", configPanel);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(mainTabbedPane, javax.swing.GroupLayout.PREFERRED_SIZE, 819, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(mainTabbedPane, javax.swing.GroupLayout.PREFERRED_SIZE, 634, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void uploadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uploadButtonActionPerformed
        // 获取修改文件列表
        ListModel<String> changeList = this.changeFilesList.getModel();
        if (changeList == null || changeList.getSize() <= 0) {
            JOptionPane.showMessageDialog(this, "No any files changed!", "Upload Files",
                    JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        //获取主机IP列表，获取登录信息
        String hosts = this.hostsTextArea.getText();
        List<String> hostList = new ArrayList<String>();
        try {
            String[] lines = hosts.split("\n");
            for (int i = 0; i < lines.length; i++) {
                System.out.println("host ip: " + lines[i]);
                String hostIp = lines[i].trim();
                if (hostIp.equals("")) {
                    continue;
                }
                if (hostIp.length() > 15) {
                    JOptionPane.showMessageDialog(this, "Host ip " + hostIp + " is not valid!", "Error",
                            JOptionPane.INFORMATION_MESSAGE);
                }
                hostList.add(hostIp);
            }

            if (hostList.isEmpty()) {
                JOptionPane.showMessageDialog(this, "You must input at least one host ip", "Error",
                        JOptionPane.INFORMATION_MESSAGE);
                return;
            }
        } catch (Exception e) {
            System.err.println("Error read host ips: " + e.getMessage());
            JOptionPane.showMessageDialog(this, e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        String usr = usernameTextField.getText().trim();
        String pwd = new String(pwdTextField.getPassword());

        if (usr.equals("")) {
            JOptionPane.showMessageDialog(this, "Please enter the username", "Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        // 上传有差异的文件或者目录
        for (int i = 0; i < hostList.size(); i++) {

            for (int k = 0; k < changeList.getSize(); k++) {
                String localFile = changeList.getElementAt(k);
                String remotePath = fileMapping.get(localFile);
                if (remotePath == null) {
                    JOptionPane.showMessageDialog(this, "Not found the remote path mapping for " + localFile, "Error",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                }
                String host = hostList.get(i);
                SCP scp = new SCP(host, usr, pwd);
                try {
                    scp.upload(localFile, remotePath);
                } catch (Exception e) {
                    String errMsg = "Fail to upload [" + localFile + "] to [" + host + "].\nError: " + e.getMessage();
                    JOptionPane.showMessageDialog(this, errMsg, "Error",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        JOptionPane.showMessageDialog(this, "Upload Finished!", "Success",
                JOptionPane.INFORMATION_MESSAGE);

        // 更新时间
        this.lastSyncTime = System.currentTimeMillis();
        this.lastSyncTimeLabel.setText(Utils.formatDate(lastSyncTime));
    }//GEN-LAST:event_uploadButtonActionPerformed

    private void showCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showCheckBoxActionPerformed
        // 显示密码
        if (showCheckBox.isSelected()) {
            pwdTextField.setEchoChar((char) 0);
        } else {
            pwdTextField.setEchoChar('*');
        }
    }//GEN-LAST:event_showCheckBoxActionPerformed

    private void resetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButtonActionPerformed
        PathMappingTableModel m = (PathMappingTableModel) this.mappingTable.getModel();
        m.removeAll();
        fileMapping.clear();
        cache.clear();
    }//GEN-LAST:event_resetButtonActionPerformed

    private void addMappingButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addMappingButtonActionPerformed
        // 打开输入路径映射的对话框
        newMappingDialog.setLocationRelativeTo(this);
        newMappingDialog.setVisible(true);
        newMappingDialog.pack();

        // 马上获取输入焦点, 另外在MAC OSX上粘帖也是使用Ctrl+V
        this.localPathTextField.requestFocusInWindow();
    }//GEN-LAST:event_addMappingButtonActionPerformed

    private void dialogOkButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dialogOkButtonActionPerformed
        // 获取本地和远程路径
        String localPath = this.localPathTextField.getText();
        String remotePath = this.remotePathTextField.getText();

        if (localPath == null
                || remotePath == null
                || "".equals(localPath.trim())
                || "".equals(remotePath.trim())) {
            JOptionPane.showMessageDialog(newMappingDialog,
                    "Please enter the local and remote path!",
                    "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        PathMappingTableModel model = (PathMappingTableModel) this.mappingTable.getModel();
        if (model.exists(localPath)) {
            JOptionPane.showMessageDialog(newMappingDialog,
                    "本地路径映射已经存在!",
                    "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }
        model.addRow(new String[]{localPath, remotePath});

        newMappingDialog.setVisible(false);
        localPathTextField.setText("");
        remotePathTextField.setText("");
    }//GEN-LAST:event_dialogOkButtonActionPerformed

    private void findChangeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_findChangeButtonActionPerformed
        //  根据给定的本地路径查找变化的文件
        DefaultTableModel model = (DefaultTableModel) this.mappingTable.getModel();
        Vector data = model.getDataVector();
        if (data == null || data.size() <= 0) {
            JOptionPane.showMessageDialog(this,
                    "Please enter a file path mapping firstly!", "Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        // 第一次没有上次的更新时间，则让用户设置
        if (this.lastSyncTime == 0) {
            String time = JOptionPane.showInputDialog("第一次想查找多长时间之前的变动(分钟):", 10);
            try {
                long t = Integer.parseInt(time);
                if (t <= 0) {
                    JOptionPane.showMessageDialog(this,
                            "Value must be > 0!", "Error",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                }

                this.lastSyncTime = System.currentTimeMillis() - t * 60 * 1000;
                this.lastSyncTimeLabel.setText(Utils.formatDate(lastSyncTime));
            } catch (NumberFormatException | HeadlessException e) {
                JOptionPane.showMessageDialog(this,
                        "Please input a integer value!", "Error",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }

        }

        // 查找变化的文件
        List<String> changeFiles = new ArrayList<>();
        for (int i = 0; i < data.size(); i++) {
            try {
                Vector row = (Vector) data.get(i);
                String localPath = (String) row.get(0);
                String remotePath = (String) row.get(1);
                List<String> files = ChangeFilesFinder.doFindChange(localPath, lastSyncTime);

                cache.put(localPath, files);

                for (int k = 0; k < files.size(); k++) {
                    String localFilePath = files.get(k);
                    String remoteFilePath = Utils.replacePathPrefix(localFilePath, localPath, remotePath);
                    fileMapping.put(localFilePath, Utils.removeFilename(remoteFilePath));
                    changeFiles.add(localFilePath);
                }

            } catch (FileNotFoundException ex) {
                // Swing的浏览文件组件有bug，无法正确处理带空格的文件路径
                JOptionPane.showMessageDialog(this,
                        "Error: " + ex.getMessage(), "Error",
                        JOptionPane.ERROR_MESSAGE);
                break;
            }
        }

        if (changeFiles.size() <= 0) {
            JOptionPane.showMessageDialog(this,
                    "Not found any files have been modified!", "Find Changed Files",
                    JOptionPane.INFORMATION_MESSAGE);
            // 清空结果不太好，还是保留上次内容
            return;
        }

        System.out.println("Changed files: " + changeFiles);

        ChangeFilesModel listModel = (ChangeFilesModel) this.changeFilesList.getModel();
        // 先清空上一次结果
        listModel.clear();
        for (String file : changeFiles) {
            listModel.addElement(file);
        }
    }//GEN-LAST:event_findChangeButtonActionPerformed

    private void deleteMappingButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteMappingButtonActionPerformed
        // 删除选中的行
        int[] selected = this.mappingTable.getSelectedRows();
        if (selected.length <= 0) {
            JOptionPane.showMessageDialog(this,
                    "Please selected a mapping firstly!", "Error",
                    JOptionPane.ERROR_MESSAGE);
        } else {
            PathMappingTableModel m = (PathMappingTableModel) this.mappingTable.getModel();

            // 删除内存中这个指定删除的项
            for (int i = 0; i < m.getRowCount(); i++) {
                if (Utils.containsInteger(selected, i)) {
                    String localPath = (String) m.getValueAt(i, 0);
                    List<String> files = cache.get(localPath);

                    // 竟然在这里发生了java.lang.NullPointerException
                    if (files == null) {
                        continue;
                    }
                    for (String f : files) {
                        fileMapping.remove(f);
                    }
                }
            }

            // 从模型中删除
            m.removeSelectedRows(selected);
        }
    }//GEN-LAST:event_deleteMappingButtonActionPerformed

    private void updateSyncTimeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateSyncTimeButtonActionPerformed
        // 更改同步时间, 目的是再次上传之前的修改
        String jumpBackTime = JOptionPane.showInputDialog("倒退多长时间（分钟）：");
        if (jumpBackTime == null || jumpBackTime.trim().equals("")) {
            // do nothing
            return;
        }

        try {
            int mins = Integer.parseInt(jumpBackTime);
            long newTime = this.lastSyncTime - mins * 60 * 1000;
            this.lastSyncTimeLabel.setText(Utils.formatDate(newTime));
            this.lastSyncTime = newTime;
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this,
                    "Error: " + e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        }

    }//GEN-LAST:event_updateSyncTimeButtonActionPerformed

    private void localBrowserButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_localBrowserButtonActionPerformed
        // 浏览本地文件
        JFileChooser chooser = new JFileChooser();
        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        int re = chooser.showOpenDialog(this.newMappingDialog);
        if (re == JFileChooser.APPROVE_OPTION) {
            File f = chooser.getSelectedFile();
            this.localPathTextField.setText(f.getAbsolutePath());
        }
    }//GEN-LAST:event_localBrowserButtonActionPerformed

    private void aboutMeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_aboutMeButtonActionPerformed
        JOptionPane.showMessageDialog(this, "SimpleFileSync " + VERSION + "\nzhangqunshi@126.com");
    }//GEN-LAST:event_aboutMeButtonActionPerformed

    private void currTimeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_currTimeButtonActionPerformed
        // 把上次更新时间改成当前时间
        this.lastSyncTime = System.currentTimeMillis();
        this.lastSyncTimeLabel.setText(Utils.formatDate(lastSyncTime));
    }//GEN-LAST:event_currTimeButtonActionPerformed

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /*
         * Set the Nimbus look and feel
         */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(MainUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(MainUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(MainUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(MainUI.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MainUI().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton aboutMeButton;
    private javax.swing.JButton addMappingButton;
    private javax.swing.JList<String> changeFilesList;
    private javax.swing.JScrollPane changeFilesScrollPane;
    private javax.swing.JPanel configPanel;
    private javax.swing.JButton currTimeButton;
    private javax.swing.JButton deleteMappingButton;
    private javax.swing.JButton dialogOkButton;
    private javax.swing.JLabel dirMappingLabel;
    private javax.swing.JButton findChangeButton;
    private javax.swing.JTextArea hostsTextArea;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel lastSyncTimeLabel;
    private javax.swing.JButton localBrowserButton;
    private javax.swing.JLabel localChangeFilesLabel;
    private javax.swing.JLabel localPathLabel;
    private javax.swing.JTextField localPathTextField;
    private javax.swing.JTabbedPane mainTabbedPane;
    private javax.swing.JTable mappingTable;
    private javax.swing.JScrollPane mappingTableScrollPane;
    private javax.swing.JDialog newMappingDialog;
    private javax.swing.JLabel passwordLabel;
    private javax.swing.JPasswordField pwdTextField;
    private javax.swing.JLabel remotePathLabel;
    private javax.swing.JTextField remotePathTextField;
    private javax.swing.JButton resetButton;
    private javax.swing.JPanel scpPanel;
    private javax.swing.JLabel serverLabel;
    private javax.swing.JScrollPane serverListScrollPane;
    private javax.swing.JCheckBox showCheckBox;
    private javax.swing.JLabel syncTimeLabel;
    private javax.swing.JButton updateSyncTimeButton;
    private javax.swing.JButton uploadButton;
    private javax.swing.JLabel usernameLabel;
    private javax.swing.JTextField usernameTextField;
    // End of variables declaration//GEN-END:variables
}
